/*
 * Copyright 2008. Mount Sinai Hospital, Toronto, Canada.
 * 
 * Licensed under the Apache License, Version 2.0. You
 * can find a copy of the license at:
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * IN NO EVENT SHALL MOUNT SINAI HOSPITAL BE LIABLE TO ANY PARTY FOR DIRECT, 
 * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST 
 * PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, 
 * EVEN IF MOUNT SINAI HOSPITAL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 
 * DAMAGE.
 * 
 * MOUNT SINAI HOSPITAL SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE AND 
 * ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". 
 * MOUNT SINAI HOSPITAL HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, 
 * UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 
 */

package com.sinai.mshab.visibility;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A concrete implementations of {@link NestedVisibilityController} that takes a
 * Tree structure approach to {@link VisibilityRule} executions. The child rules
 * are executed before the parent rules to insure that parent rules override the
 * child rules.
 * 
 * @author Arthur Kalmenson
 */
public class VisibilityControllerTree implements NestedVisibilityController {

	/**
	 * The root of the tree.
	 */
	private VisibilityControllerTreeNode root;

	/**
	 * A map of {@link VisibilityRule} to {@link VisibilityControllerTreeNode}
	 * to assist in adding new children.
	 */
	private Map<VisibilityRule<?, ?, ?>, VisibilityControllerTreeNode> ruleToTreeNode = new HashMap<VisibilityRule<?, ?, ?>, VisibilityControllerTreeNode>();

	/**
	 * Creates a new <code>VisibilityControllerTree</code> with the given root
	 * node.
	 * 
	 * @param root
	 *            the root {@link VisibilityRule}.
	 */
	public VisibilityControllerTree(VisibilityRule<?, ?, ?> root) {
		if (root == null) {
			throw new IllegalArgumentException("All constructor arguments for "
					+ getClass().getName() + " must be instantiated.");
		}

		this.root = new VisibilityControllerTreeNode(root);
		ruleToTreeNode.put(root, this.root);
	}

	public void addParentChildRule(VisibilityRule<?, ?, ?> parent,
			VisibilityRule<?, ?, ?> child) {

		// insure child and parent are valid.
		if (parent == null || child == null) {
			throw new IllegalArgumentException(getClass().getName()
					+ ": Parent and child must both be instantiated.");
		}

		// get the node and insure we actually have this parent.
		VisibilityControllerTreeNode node = ruleToTreeNode.get(parent);
		if (node == null) {
			throw new IllegalStateException(getClass().getName()
					+ ": Parent must be in controller before adding children.");
		}

		// now create a new node for the child and add it to the parent.
		VisibilityControllerTreeNode childNode = new VisibilityControllerTreeNode(
				child);
		ruleToTreeNode.put(child, childNode);
		node.addChild(childNode);
	}

	public void executeAll() {
		executeRecursively(root);
	}

	/**
	 * Executes all the children of <code>root</code> and then executes root
	 * itself. This method is recursive and goes through all the children's
	 * children, etc.
	 * 
	 * @param root
	 *            the {@link VisibilityControllerTreeNode} and children to
	 *            execute.
	 */
	private void executeRecursively(VisibilityControllerTreeNode root) {

		// recursively call all children.
		List<VisibilityControllerTreeNode> children = root.getChildren();
		for (VisibilityControllerTreeNode child : children) {
			executeRecursively(child);
		}

		// now execute self.
		root.getValue().execute();
	}

	public void setRootRule(VisibilityRule<?, ?, ?> root) {

		// since we're adding a new root, we need to clean up.
		ruleToTreeNode.clear();
		this.root = new VisibilityControllerTreeNode(root);
		ruleToTreeNode.put(root, this.root);
	}

	public int size() {
		return ruleToTreeNode.size();
	}
}
